#include "t0022-0.h"
#include "cui.h"
#include <dlfcn.h>

#define MODNAME     "[WDT]"

/*
* Widget Searching functions
*/
struct cui_widget_st* get_widget_byname(struct list_head *widgetlist, const char *name)
{
    // ERRSYS_INFOPRINT("!!!!!!!!!get_widget_byname widgetlist %p\n", widgetlist);
    // ERRSYS_INFOPRINT("!!!!!!!!!get_widget_byname %p %p\n", widgetlist->prev, widgetlist->next);
    if (!list_empty(widgetlist)) {
        struct list_head *pos = NULL;
        list_for_each(pos, widgetlist) {
            struct cui_widget_st *widget = list_entry(pos, struct cui_widget_st, node);
            if (is_string_equal_safe(name, widget->name)) {
                return widget;
            }
            if (!list_empty(&widget->widgetlist)) {
                if ((widget = get_widget_byname(&widget->widgetlist, name)) != NULL) {
                    return widget;
                }
            }
        }
    }
    return NULL;
}

struct cui_widget_st* get_widget_byid(struct list_head *widgetlist, int id)
{
    if (!list_empty(widgetlist)) {
        struct list_head *pos = NULL;
        list_for_each(pos, widgetlist) {
            struct cui_widget_st *widget = list_entry(pos, struct cui_widget_st, node);
            if (id == widget->id) {
                return widget;
            }
            if (!list_empty(&widget->widgetlist)) {
                if ((widget = get_widget_byid(&widget->widgetlist, id)) != NULL) {
                    return widget;
                }
            }
        }
    }
    return NULL;
}

int cui_eventslots_parse(cJSON *attr, struct cui_widget_st* widget)
{
    cJSON *actionlist;
    actionlist = cJSON_GetObjectItem(attr, CUI_CONFIELD_ACTIONLIST);

    if (cJSON_ValidArray(actionlist)) {
        int i;
        int nactions = cJSON_GetArraySize(actionlist);
        void *handle = dlopen(NULL, RTLD_LAZY);
        if (handle != NULL) {
            for (i = 0;i < nactions;i ++) {
                cJSON *event, *slot, *action;
                action = cJSON_GetArrayItem(actionlist, i);
                event = cJSON_GetObjectItem(action, CUI_CONFIELD_EVENT);
                slot = cJSON_GetObjectItem(action, CUI_CONFIELD_SLOT);
                if (cJSON_ValidString(event) && cJSON_ValidString(slot)) {
                    void *func = dlsym(handle, slot->valuestring);
                    if (func != NULL) {
                        ERRSYS_INFOPRINT("[%s]:%s:%s installed\n", event->valuestring, widget->name, slot->valuestring);
                        if (is_string_equal_safe(event->valuestring, CUI_CONFIELD_KEYEVENT)) {
                            widget->cb.keyevent = func;
                        }
                        else if (is_string_equal_safe(event->valuestring, CUI_CONFIELD_MOUSEEVENT)) {
                            widget->cb.mouseevent = func;
                        }
                        else {
                            ERRSYS_INFOPRINT("[event/slot]:%s => %s not implemented\n", event->valuestring, slot->valuestring);
                        }
                    }
                    else {
                        ERRSYS_INFOPRINT("[event/slot]:%s => %s missing (%s)\n", event->valuestring, slot->valuestring, dlerror());
                    }
                }
            }
            dlclose(handle);
        }
    }


    return 0;
}

int cui_set_widget_eventslot(struct cui_widget_st *widget, int eventtype, CUI_EVENT_CALLBACK cb)
{
    int retval = -1;
    switch (eventtype) {
        case CUI_EVENTCB_TYPE_KEYEVENT:
            widget->cb.keyevent = cb;
            retval = 0;
            break;
        case CUI_EVENTCB_TYPE_MOUSEEVENT:
            widget->cb.mouseevent = cb;
            retval = 0;
            break;
        case CUI_EVENTCB_TYPE_PAINT:
            widget->cb.paint = cb;
            retval = 0;
            break;
    }

    return retval;
}

void cui_set_widget_title(struct cui_widget_st *widget, const char *title)
{
    ERRSYS_INFOPRINT("[%s]%s enable title\n", widget->name, __func__);
    memset(widget->title, 0, CUI_WIDGETTITLE_BUFLEN);
    strncpy(widget->title, title, CUI_WIDGETTITLE_BUFLEN);
    widget->flags |= CUI_WIDGET_FLAGS_TITLE;
}

void cui_widget_resize(struct cui_widget_st *widget, int lines, int cols, int beginy, int beginx)
{
    ERRSYS_INFOPRINT("%s\n", __func__);
    if (widget && widget->widgetinstance) {
        widget->lines = lines >= 0 ? lines : widget->lines;
        widget->cols = cols >= 0 ? cols : widget->cols;
        widget->beginy = beginy >= 0 ? beginy : widget->beginy;
        widget->beginx = beginx >= 0 ? beginx : widget->beginx;
        wresize(widget->win, widget->lines, widget->cols);
        mvwin(widget->win, widget->beginy, widget->beginx);
    }
}

int cui_widget_align(struct cui_widget_st *widget, int flags)
{
    int retval = -1;
    if (widget) {

        int scrrows, scrcols;
        getmaxyx(stdscr, scrrows, scrcols);

        // 计算居中位置
        if (flags & CUI_WIDGET_ALIGNMENT_XMID) {
            widget->beginx = (scrcols >> 1) - (widget->cols >> 1);
        }

        if (flags & CUI_WIDGET_ALIGNMENT_YMID) {
            widget->beginy = (scrrows >> 1) - (widget->lines >> 1);
        }

        wresize(widget->win, widget->lines, widget->cols);
        mvwin(widget->win, widget->beginy, widget->beginx);

        // cui_dump_form(widget);
        // cui_dump_widget(widget);
        retval = 0;
    }
    return retval;
}

typedef int (*widget_initialize_callback)(struct cui_widget_st*);

int cui_install_initializer(struct cui_widget_st *widget)
{
    void *handle = NULL;
    handle = dlopen(NULL, RTLD_LAZY);
    ERRSYS_INFOPRINT("cui_set_widget_initializer ops %p\n", widget->ops);
    // widget->ops.initialize = dummy_initialize;
    if (strlen(widget->name) != 0) {
        widget_initialize_callback cb = NULL;
        static char callback_default[512] = {0};
        GET_EXPORT_WIDGET_INITIALIZE_STRING(widget->name, callback_default, 512);
        cb = dlsym(handle, callback_default);
        if (cb != NULL) {
            widget->ops.initialize = cb;
            ERRSYS_INFOPRINT("[%s]: initializer is ready (%s:widget:%p cb:%p)\n", widget->name, callback_default, widget, widget->ops.initialize);
        }
        else {
            ERRSYS_INFOPRINT("[%s]: initializer is ready (dummy_initialize:widget:%p cb:%p)\n", widget->name, widget, widget->ops.initialize);
        }
    }
    if (handle != NULL) {
        dlclose(handle);
    }
    return 0;    
}

void cui_set_border_highlight(struct cui_widget_st* widget, int isset)
{
    // if (isset) {
    //     ERRSYS_INFOPRINT("%s %s\n", widget->name, __func__);
    //     WINDOW *win = widget->ops.getwin(widget);
    //     attron(COLOR_PAIR(GRNBLK));
    //     mvprintw(LINES - 3, 0, "Use 'm' for moving, 'r' for resizing");
    //     mvwhline(win, 2, 1, ACS_HLINE, widget->cols - 2); 
    //     // wborder(widget->ops.getwin(widget),'|', '|', '-', '-', '+', '+', '+', '+');
    //     attroff(COLOR_PAIR(GRNBLK));
    //     refresh();
	// 	update_panels();
	// 	doupdate();
    // }
    // else {
    //     wborder(widget->ops.getwin(widget),' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ');
    // }
    
}

void cui_widget_destroy(struct cui_widget_st *widget)
{
    if (widget != NULL) {
        ERRSYS_INFOPRINT("[%s]%s destroy widget %p\n", widget->name, __func__, widget);
        if (widget->win) {
            delwin(widget->win);
        }
        if (widget->clabel) {
            free(widget->clabel);
        }
        list_del(&widget->node);
        free(widget);
    }
}

struct cui_widget_st* cui_widget_dup(struct cui_widget_st* template)
{
    struct cui_widget_st* widget = NULL;
    struct cui_label_st *clabel = NULL;
    int retval = -1;

    if (template) {
        widget = cui_new_widget(-1, template->cscr, &template->ops, template, NULL);;
        if (widget) {
            /* copy widget */
            widget->lines = template->lines;
            widget->cols = template->cols;
            widget->beginy = template->beginy;
            widget->beginx = template->beginx;
            widget->flags = template->flags;
            memcpy(&widget->border, &template->border, sizeof(struct cui_widget_border_st));

            if (template->nlabels != 0) {
                clabel = zmalloc(template->nlabels *  sizeof(struct cui_label_st));
                if (clabel) {
                    widget->clabel = clabel;
                    widget->nlabels = template->nlabels;
                    memcpy(widget->clabel, template->clabel, template->nlabels *  sizeof(struct cui_label_st));
                }
                else {
                    goto err;
                }
            }

            retval = 0;
        }
    }

err:
    if (retval < 0) {
        if (widget)
            free(widget);
        if (clabel)
            free(clabel);
    }
    return widget;
}

struct cui_widget_st* cui_new_widget(int id, struct cui_screen_st *cscr, struct cui_widget_operations *widgetops, struct cui_widget_st* parent, struct cui_widget_st* sibling)
{
    struct cui_widget_st *widget = zmalloc(sizeof(struct cui_widget_st));

    if (widget == NULL) {
        ERRSYS_INFOPRINT("Fail to malloc for widget or ops\n");
        goto err;
    }

    // full screen as default
    widget->beginx = 0;
    widget->beginy = 0;
    widget->cols = COLS;
    widget->lines = LINES;
    widget->border.ls = '|';    // left side
    widget->border.rs = '|';    // right side
    widget->border.ts = '-';    // top side
    widget->border.bs = '-';    // bottom side
    widget->border.tl = '+';    // top left corner
    widget->border.tr = '+';    // top right corner
    widget->border.bl = '+';    // bottom left corner
    widget->border.br = '+';    // bottom right corner

    widget->sibling = sibling;
    widget->parent = parent;
    widget->id = id;
    widget->cscr = cscr;
    INIT_LIST_HEAD(&widget->widgetlist);
    INIT_LIST_HEAD(&widget->node);

    memcpy(&widget->ops, widgetops, sizeof(struct cui_widget_operations));

err:
    return widget;
}

cJSON* cui_guess_widgetnode(cJSON *nodewidget)
{
    cJSON *node = NULL;
    // try guess the type of widget
    node = cJSON_GetObjectItem(nodewidget, CUI_CONFIELD_MENU);
    if (node != NULL) {
        goto out;
    }
    node  = cJSON_GetObjectItem(nodewidget, CUI_CONFIELD_WINDOW);
    if (node != NULL) {
        goto out;
    }
    node  = cJSON_GetObjectItem(nodewidget, CUI_CONFIELD_PANEL);
    if (node != NULL) {
        goto out;
    }
    node  = cJSON_GetObjectItem(nodewidget, CUI_CONFIELD_FORM);
    if (node != NULL) {
        goto out;
    }
out:
    return node;
}

static int nextrefspath(const char* ref, char delimit)
{
    int retval = -1;
    int n = strlen(ref);
    static char tmp[256] = {0};
    // ERRSYS_INFOPRINT("%s ref (%s) n %d\n", __func__, ref, n);
    if (n > 0) {
        int i;
        for (i = 0;i < n;i ++) {
            if (ref[i] == delimit) {
                retval = i;
                break;
            }
        }

        if (i == n) {
            retval = n;
        }
    }
    if (retval > 0) {
        memset(tmp, 0, 256);
        memcpy(tmp, ref, retval);
        // ERRSYS_INFOPRINT("from %s get %s len %d\n", ref, tmp, retval);
    }
    // else {
    //     ERRSYS_INFOPRINT("from %s get null\n", ref);
    // }

    return retval;
}

void cui_set_widget_layout(struct cui_widget_st* widget, const char *reference) {
    struct cui_screen_st *cscr = widget->cscr;
    struct cui_layoutgroup_st *layoutgroup;
    struct cui_layout_st *layout;
    int gnreflen = nextrefspath(reference, '/');
    int reflen = strlen(reference);
    // ERRSYS_INFOPRINT("!!!!!!! cui_set_widget_layout BEGIN %s !!!!!!\n", reference);

    if (gnreflen != 0) {
        list_for_each_entry(layoutgroup, &cscr->layoutgrouplist, node) {
            if (gnreflen == strlen(layoutgroup->name) && is_string_equal_safe2(reference, layoutgroup->name, gnreflen) && (gnreflen + 1) < reflen) {
                int loreflen = nextrefspath(&reference[gnreflen + 1], '/');
                // ERRSYS_INFOPRINT("[%s]layout group is found\n", layoutgroup->name);
                if (loreflen != 0) {
                    list_for_each_entry(layout, &layoutgroup->layoutlist, node) {
                        if (loreflen == strlen(layout->name) && is_string_equal_safe2(&reference[gnreflen + 1], layout->name, loreflen)) {
                            struct cui_widget_st* parent = widget->parent;
                            double lines = LINES;
                            double cols = COLS;
                            ERRSYS_INFOPRINT("[%s]layout is found\n", layout->name);
                            ERRSYS_INFOPRINT("[%s][%s]rows:%f cols:%f x:%f y:%f flags:%x\n", widget->name, reference, layout->lines, layout->cols, layout->beginx, layout->beginy, layout->flags);
                            if (parent != NULL) {
                                lines = parent->lines;
                                cols = parent->cols;
                                ERRSYS_INFOPRINT("[%s] derived from parent widget rows:%f cols:%f\n", widget->name, lines, cols);
                            }
                            else {
                                ERRSYS_INFOPRINT("[%s] derived from full screen widget rows:%f cols:%f\n", widget->name, lines, cols);
                            }

                            if (IS_LAYOUT_PERCENT(layout, BEGINX)) {
                                widget->beginx = cols * layout->beginx;
                            }
                            else {
                                widget->beginx = layout->beginx;
                            }
                            if (IS_LAYOUT_PERCENT(layout, BEGINY)) {
                                widget->beginy = lines * layout->beginy;
                            }
                            else {
                                widget->beginy = layout->beginy;
                            }
                            if (IS_LAYOUT_PERCENT(layout, LINES)) {
                                widget->lines = lines * layout->lines;
                            }
                            else {
                                widget->lines = lines - widget->beginy;
                                // ERRSYS_INFOPRINT("##### [%s] lines(%f) - beginy(%d) widget->lines(%d)\n", layout->name, lines, widget->beginy, widget->lines);
                            }
                            if (IS_LAYOUT_PERCENT(layout, COLS)) {
                                widget->cols = cols * layout->cols;
                            }
                            else {
                                widget->cols = cols - widget->beginx;
                                // ERRSYS_INFOPRINT("##### [%s] cols(%f) - beginx(%d) widget->cols(%d)\n", layout->name, cols, widget->beginx, widget->cols);
                            }
                            ERRSYS_INFOPRINT("##### [%s] rows:%d cols:%d x:%d y:%d\n", widget->name, widget->lines, widget->cols, widget->beginx, widget->beginy);
                        }
                    }

                }
            }
        }
    }

    // ERRSYS_INFOPRINT("!!!!!!! cui_set_widget_layout END !!!!!!\n");
}

// static const char * cui_opstype_name(int opstype)
// {
//     const char *opsname = "unknown";
//     switch (opstype) {
//         case CUI_WIDGET_OPS_RECURSIVE_TYPE_CREATE:
//             opsname = "create";
//             break;
//         case CUI_WIDGET_OPS_RECURSIVE_TYPE_PAINT:
//             opsname = "paint";
//             break;
//         case CUI_WIDGET_OPS_RECURSIVE_TYPE_CUSTOMIZED_INITIALIZE:
//             opsname = "initialize";
//             break;
//     }
//     return opsname;
// }


int cui_ops_widget_recursive_widthfirst(struct list_head *widgetlist, int opstype)
{
    int retval = -1;

    // create all the window objects
    // ERRSYS_INFOPRINT("cui_ops_widget_recursive_widthfirst BEGIN widgetlist %p\n", widgetlist);
    if (!list_empty(widgetlist)) {
        struct list_head *pos = NULL, *n;
        struct list_head *subwidgetlist = NULL;
        list_for_each_safe(pos, n, widgetlist) {
            struct cui_widget_st* widget = list_entry(pos, struct cui_widget_st, node);
            // ERRSYS_INFOPRINT("Recur %s on [%s]\n", cui_opstype_name(opstype), widget->name);
            retval = -1;
            switch (opstype) {
                case CUI_WIDGET_OPS_RECURSIVE_TYPE_CREATE:
                    retval = widget->ops.create(widget);
                    subwidgetlist = &widget->widgetlist;
                    // ERRSYS_INFOPRINT("!!!!!! widget %p cscr %p retval %d !!!!!!\n", widget, widget->cscr, retval);
                    // {
                    //     struct cui_widget_st *widget_tmp = get_widget_byname(&widget->cscr->windowlist, "dxtviewer");
                    //     ERRSYS_INFOPRINT("!!!!!! widget %p !!!!!!\n", widget_tmp);
                    //     ERRSYS_INFOPRINT("!!!!!! init %p !!!!!!\n", widget_tmp->ops->initialize);
                    // }
                    break;
                case CUI_WIDGET_OPS_RECURSIVE_TYPE_PAINT:
                    widget->ops.paint(widget);
                    subwidgetlist = &widget->widgetlist;
                    retval = 0;
                    break;
                case CUI_WIDGET_OPS_RECURSIVE_TYPE_CUSTOMIZED_INITIALIZE:
                    // ERRSYS_INFOPRINT("### CUSTOMIZED_INITIALIZE BEGIN ###\n");
                    cui_dump_widget_hierarchy(widget);
                    // ERRSYS_INFOPRINT("call initializer (%s:widget:%p cb:%p)\n", widget->name, widget, widget->ops.initialize);
                    widget->ops.initialize(widget);
                    // ERRSYS_INFOPRINT("### CUSTOMIZED_INITIALIZE END ###\n");
                    subwidgetlist = &widget->widgetlist;
                    retval = 0;
                    break;
            }
            if (retval < 0) {
                goto out;
            }
            if (subwidgetlist != NULL && !list_empty(subwidgetlist)) {
                // if (opstype != CUI_WIDGET_OPS_RECURSIVE_TYPE_CUSTOMIZED_INITIALIZE) {
                // ERRSYS_INFOPRINT("Recursively %s on [%s] subwidgetlist %p\n", cui_opstype_name(opstype), widget->name, subwidgetlist);
                retval = cui_ops_widget_recursive_widthfirst(subwidgetlist, opstype);
                // }
            }
        }
    }
out:
    // ERRSYS_INFOPRINT("cui_ops_widget_recursive_widthfirst END widgetlist %p\n", widgetlist);
    return retval;
}

int cui_ops_widget_recursive_depthfirst(struct list_head *widgetlist, int opstype)
{
    /* 
    * return -1, stop event propagation
    * return 0, continue event propagation
    */
    int retval = 0;
    // create all the window objects
    if (!list_empty(widgetlist)) {
        struct list_head *pos = NULL, *n;
        list_for_each_safe(pos, n, widgetlist) {
            struct cui_widget_st* widget = list_entry(pos, struct cui_widget_st, node);
            retval = 0;
            switch (opstype) {
                case CUI_WIDGET_OPS_RECURSIVE_TYPE_DESTROY:
                    // ERRSYS_INFOPRINT("[%s] %s\n", widget->name, __func__);
                    if (!list_empty(&widget->widgetlist)) {
                        cui_ops_widget_recursive_depthfirst(&widget->widgetlist, opstype);
                    }
                    // ERRSYS_INFOPRINT("[%s] %s before\n", widget->name, __func__);
                    widget->ops.destroy(widget);
                    // ERRSYS_INFOPRINT("[%s] %s after\n", widget->name, __func__);
                    retval = 0;
                    break;
            }
            if (retval < 0) {
                goto out;
            }
        }
    }
out:
    return retval;
}

struct cui_widget_st* cui_get_default_inputwidget(struct cui_widget_st* widget)
{
    struct cui_widget_st* inputwidget = NULL;
    // ERRSYS_INFOPRINT("#### %p BEGIN ####\n", widget);
    if (widget != NULL) {
        if (IS_WIDGET_INPUTEVENT(widget)) {
            inputwidget = widget;
        }
        else {
            struct cui_widget_st* subwidget;
            list_for_each_entry(subwidget, &widget->widgetlist, node) {
                inputwidget = cui_get_default_inputwidget(subwidget);
                if (inputwidget != NULL) {
                    break;
                }

            }
        }
    }

    // ERRSYS_INFOPRINT("#### %p END inputwidget %p  ####\n", widgetlist, inputwidget);
    return inputwidget;
}
